package org.example.jnautil.key;

import basicutil.common.ListUtil;
import basicutil.common.ThreadUtil;
import javafx.util.Pair;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 产生按键消息
 * 可以注册监听器来相应特定按键消息
 */
@Slf4j
public final class KeyQueueBinder {
    public static final KeyQueueBinder INSTANCE = new KeyQueueBinder();
    private static final KeyBoardManager keyBoardManager = KeyBoardManager.INSTANCE;
    private final EnumMap<KeyCodeEnum, StatefulKeyListener> longDetectKey = new EnumMap<>(KeyCodeEnum.class);
    List<RegisterItem> registered = new ArrayList<>();

    Map<Predicate<String>, Pair<BlockingQueue<String>, Boolean>> statelessKey = new HashMap<>();
    AtomicBoolean initialized = new AtomicBoolean(false);

    private KeyQueueBinder() {
    }

    public List<RegisteredKey> allListener() {
        List<RegisteredKey> result = new ArrayList<>(registered);
        for (Map.Entry<Predicate<String>, Pair<BlockingQueue<String>, Boolean>> entry : statelessKey.entrySet()) {
            if (entry.getKey() instanceof StringPredicate) {

                result.add(new RegisteredKey() {
                    final String id = ((StringPredicate) entry.getKey()).id;
                    final BlockingQueue<String> queue = entry.getValue().getKey();


                    @Override
                    public String keyId() {
                        return id;
                    }

                    @Override
                    public boolean isRunning() {
                        return entry.getValue().getValue();
                    }

                    @Override
                    public boolean isBlocking() {
                        return false;
                    }

                    @Override
                    public BlockingQueue<String> getQueue() {
                        return queue;
                    }
                });
            }
        }
        return result;
    }

    public BlockingQueue<String> registerStatelessKey(KeyCodeEnum... code) {
        if (!initialized.get()) {
            StatelessListener ctrl = new MyStatelessListener();
            keyBoardManager.addListener2Handler(ctrl);
            initialized.set(true);
        }
        String id = KeyCodeEnum.combineName(code);
        BlockingQueue<String> q = new ArrayBlockingQueue<>(10);
        Predicate<String> predicate = new StringPredicate(id);
        statelessKey.put(predicate, new Pair<>(q, true));
        return q;
    }

    public BlockingQueue<String> registerStatefulKey(KeyCodeEnum code1, KeyCodeEnum code2) {

        QueuedStatefulListener tListener;

        if (code2 != null) {


            validateTriggerCode(code2);
            validateOptionCode(code1);

            tListener = new QueuedStatefulListener(code2.getCode()) {
                @Override
                public ListenKeyResult onKeyEvent(HookKeyHandler hookKeyHandler, boolean keyDown) {
                    if (!keyDown) {
                        return ListenKeyResult.PASS;
                    }
                    if (hookKeyHandler.getKeyState(code1.getCode())) {
                        String msg = KeyCodeEnum.combineName(code1, code2);
                        if (!super.queue.offer(msg)) {
                            log.error("推送按键消息到队列失败，丢弃消息：{}", msg);
                        }
                        return ListenKeyResult.NOTPASS;
                    }
                    return ListenKeyResult.PASS;
                }
            };
        } else {


            validateTriggerCode(code1);
            tListener = new QueuedStatefulListener(code1.getCode()) {
                @Override
                public ListenKeyResult onKeyEvent(HookKeyHandler hookKeyHandler, boolean keyDown) {
                    if (!keyDown) {
                        return ListenKeyResult.PASS;
                    }

                    String msg = KeyCodeEnum.combineName(code1, null);
                    if (!super.queue.offer(msg)) {
                        log.error("推送按键消息到队列失败，丢弃消息：{}", msg);
                    }
                    return ListenKeyResult.NOTPASS;

                }
            };
        }
        BlockingQueue<String> queue = tListener.getQueue();
        RegisterItem registerItem = new RegisterItem(code1, code2, tListener, queue);
        registered.add(registerItem);
        activeStatefulKey(code1, code2);
        return queue;
    }

    public void unregisterStatefulKey(KeyCodeEnum code1, KeyCodeEnum code2) {
        deactiveStatefulKey(code1, code2);
        String combineName = KeyCodeEnum.combineName(code1, code2);
        RegisterItem item1 = registered.stream()
                .filter(item -> item.keyId().equals(combineName))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("未注册的热键：" + combineName));
        registered.remove(item1);
    }

    public void activeStatefulKey(KeyCodeEnum code1, KeyCodeEnum code2) {
        String combineName = KeyCodeEnum.combineName(code1, code2);
        RegisterItem item1 = registered.stream()
                .filter(item -> item.keyId().equals(combineName))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("未注册的热键：" + combineName));
        if (item1.isRunning()) {
            return;
        }
        item1.setRunning(true);
        // 注册到HookKeyHandler
        keyBoardManager.addListener2Handler(item1.tListener);
        addLongDetectKey(item1.firstCode);

    }

    public void deactiveStatefulKey(KeyCodeEnum code1, KeyCodeEnum code2) {
        String combineName = KeyCodeEnum.combineName(code1, code2);
        RegisterItem item1 = registered.stream()
                .filter(item -> item.keyId().equals(combineName))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("未注册的热键：" + combineName));
        if (!item1.isRunning()) {
            return;
        }
        item1.setRunning(false);
        // 注册到HookKeyHandler
        keyBoardManager.delListener2Handler(item1.tListener);
        delLongDetectKey(item1.firstCode);

    }

    private void addLongDetectKey(KeyCodeEnum optionCode) {
        if (optionCode == null || optionCode == KeyCodeEnum.VK_LCONTROL || optionCode == KeyCodeEnum.VK_LMENU) {
            return;
        }
        StatefulKeyListener l = longDetectKey.computeIfAbsent(optionCode, k -> new LongPressDetectListener(optionCode.getCode()));
        keyBoardManager.addListener2Handler(l);
    }

    private void delLongDetectKey(KeyCodeEnum optionCode) {
        List<RegisterItem> collect = registered.stream()
                .filter(t -> t.getFirstCode() == optionCode)
                .filter(RegisterItem::isRunning)
                .collect(Collectors.toList());
        if (collect.isEmpty()) {
            StatefulKeyListener l = longDetectKey.remove(optionCode);
            keyBoardManager.delListener2Handler(l);
        }
    }

    private void validateTriggerCode(KeyCodeEnum code) {
        if (code == null) {
            throw new IllegalArgumentException("按键不能为空");
        }
        List<RegisterItem> existed;
        if (!(existed = findByTriggerCode(code)).isEmpty() || !(existed = findByOptionCode(code)).isEmpty()) {
            throw new IllegalArgumentException(
                    String.format("按键TriggerCode：%s 已被注册，已注册按键为：%s", code, ListUtil.map(existed, RegisterItem::keyId)));
        }
    }

    private void validateOptionCode(KeyCodeEnum code) {
        if (code == null) {
            throw new IllegalArgumentException("组合键第一个键按键不能为空");
        }
        List<RegisterItem> existed;
        if (!(existed = findByTriggerCode(code)).isEmpty()) {
            throw new IllegalArgumentException(
                    String.format("按键OptionCode：%s 已被注册，已注册按键为：%s", code, ListUtil.map(existed, RegisterItem::keyId)));
        }
    }

    public List<RegisterItem> findByTriggerCode(KeyCodeEnum code) {
        return registered.stream().filter(t -> t.getSecondCode() == code).collect(Collectors.toList());
    }

    public List<RegisterItem> findByOptionCode(KeyCodeEnum code) {
        return registered.stream().filter(t -> t.getFirstCode() == code).collect(Collectors.toList());
    }

    public void unregisterStatelessKey(KeyCodeEnum code1, KeyCodeEnum code2) {
        Predicate<String> predicate = new KeyQueueBinder.StringPredicate(KeyCodeEnum.combineName(code1, code2));
        statelessKey.remove(predicate);

    }

    public void activeStatelessKey(KeyCodeEnum code1, KeyCodeEnum code2) {
        Predicate<String> predicate = new KeyQueueBinder.StringPredicate(KeyCodeEnum.combineName(code1, code2));
        Pair<BlockingQueue<String>, Boolean> pair = statelessKey.get(predicate);
        if (pair != null) {
            statelessKey.put(predicate, new Pair<>(pair.getKey(), true));
        }

    }

    public void deactiveStatelessKey(KeyCodeEnum code1, KeyCodeEnum code2) {
        Predicate<String> predicate = new KeyQueueBinder.StringPredicate(KeyCodeEnum.combineName(code1, code2));
        Pair<BlockingQueue<String>, Boolean> pair = statelessKey.get(predicate);
        if (pair != null) {
            statelessKey.put(predicate, new Pair<>(pair.getKey(), false));
        }

    }

    static abstract class QueuedStatefulListener extends StatefulKeyListener {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);

        public QueuedStatefulListener(int interestKeyCode) {
            super(interestKeyCode);
        }

        public BlockingQueue<String> getQueue() {
            return queue;
        }

    }

    @Getter
    static class RegisterItem implements RegisteredKey {
        final KeyCodeEnum firstCode;
        final KeyCodeEnum secondCode;
        final StatefulKeyListener tListener;
        final BlockingQueue<String> queue;
        @Setter
        boolean running;

        public RegisterItem(KeyCodeEnum firstCode, KeyCodeEnum secondCode, StatefulKeyListener tListener, BlockingQueue<String> queue) {
            this.firstCode = firstCode;
            this.secondCode = secondCode;
            this.tListener = tListener;
            this.queue = queue;
            this.running = false;
        }

        @Override
        public String keyId() {
            return KeyCodeEnum.combineName(firstCode, secondCode);
        }

        @Override
        public boolean isBlocking() {
            return true;
        }

        @Override
        public KeyCodeEnum firstKey() {
            return firstCode;
        }

        @Override
        public KeyCodeEnum secondKey() {
            return secondCode;
        }
    }

    @EqualsAndHashCode
    public static class StringPredicate implements Predicate<String> {
        private final String id;

        public StringPredicate(String id) {
            this.id = id;
        }

        @Override
        public String toString() {
            return id;
        }

        @Override
        public boolean test(String s) {
            return id.equals(s) || KeyCodeEnum.VK_UNDEFINED.getReadableName().equals(id);
        }
    }

    private class MyStatelessListener implements StatelessListener {
        List<Integer> alreadyDown = new CopyOnWriteArrayList<>();
        volatile long pressStartTime;

        public MyStatelessListener() {
            ThreadUtil.runLaterSafely(() -> {
                // 更优雅的写法：监听list里面的原始，如果存在时间超过指定时长，调用api查询当前键盘状态，如果没有按下就删掉他
                while (true) {
                    try {
                        if (System.currentTimeMillis() - pressStartTime > 5000) {
                            alreadyDown.clear();
                        }
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        throw new RuntimeException(e);
                    }
                }
            });
        }

        @Override
        public void onKeyEvent(int vkCode, boolean keyDown, boolean[] keyTableState) {
            log.debug("无状态监听器 接受到消息：{} {}，", vkCode, (keyDown ? "按下" : "弹起"));

            pressStartTime = System.currentTimeMillis();
            if (!keyDown) {
                alreadyDown.remove((Object) vkCode);
                log.debug("无状态监听器 接受到弹起消息，易忽略");
                return;
            }
            if (!alreadyDown.contains(vkCode)) {
                alreadyDown.add(vkCode);
            }

            String id = alreadyDown.stream().map(KeyCodeEnum::getKeyNameByCode).collect(Collectors.joining(" + "));
            String ids = id + "\t" + alreadyDown;

            for (Map.Entry<Predicate<String>, Pair<BlockingQueue<String>, Boolean>> entry : statelessKey.entrySet()) {
                log.debug("开始匹配匹配 {}， {}", entry, ids);
                if (entry.getKey().test(id)) {
                    if (entry.getValue().getValue()) {
                        log.debug("推送按键消息,按键码：{}，队列hash： {}", vkCode, entry.getValue().getKey().hashCode());
                        if (!entry.getValue().getKey().offer(ids)) {
                            log.error("推送按键消息到队列失败，丢弃的按键码：{}， keyDown： {}", vkCode, keyDown);
                        } else {
                            log.debug("推送到队列成功，{}", entry.getValue().getKey().hashCode());
                        }
                    } else {
                        log.debug("匹配到{}，但未激活", entry.getKey());
                    }

                } else {
                    log.debug("无法匹配 {}， {}", entry.getKey(), ids);
                }
            }
        }
    }
}
